home *** CD-ROM | disk | FTP | other *** search
/ PC World 2008 September / PCWorld_2008-09_cd.bin / v cisle / sadanastroju / autocomplete_manager-2.3-fx.xpi / chrome / acmanager.jar / content / acpopup.js next >
Text File  |  2008-03-14  |  48KB  |  1,306 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the License.
  12.  *
  13.  * The Original Code is the Autocomplete Manager extension.
  14.  *
  15.  * The Initial Developer of the Original Code is
  16.  * Nikitas Liogkas <nikitas@acm.org>.
  17.  * Portions created by the Initial Developer are Copyright (C) 2005-2008
  18.  * the Initial Developer. All Rights Reserved.
  19.  *
  20.  * Contributor(s): James Ravn, Heiwad Osman
  21.  * Version: 2.3
  22.  *
  23.  * ***** END LICENSE BLOCK ***** */
  24.  
  25. // acpopup.js
  26. // implements the location bar and popup functionality; retrieves all 
  27. // matching entries from the filterer and displays them in a custom popup
  28. // that contains a tree with the suggestions ordered by the desired criterion
  29.  
  30. const acm_atomService = Components.classes["@mozilla.org/atom-service;1"]
  31.                           .getService(Components.interfaces.nsIAtomService);
  32.  
  33. const ACM_LOAD_TAB_IN_BACKGROUND = "browser.tabs.loadInBackground";
  34.  
  35. // popup parameters
  36. var acm_popupExists         = false;
  37. var acm_preventPopup        = false;  // hack to prevent re-display of popup when pressing Shift+Del
  38. var acm_disablePopup        = false;  // temporarily disable popup at user's will
  39. var acm_disableInlining     = false;  // prevent inlining after a Del or a Backspace
  40. var acm_typedPrefix         = null;   // the prefix the user has typed in so far
  41. var acm_tooltip            = null;   // the tooltip to display on a popup re-sorting
  42. var acm_tooltipTimeout      = null;   // the id of the tooltip timeout call
  43. var acm_shiftPressed        = false;
  44.  
  45. var acm_ontextenteredHandler = null;  // the original handler code that we override
  46. var acm_loadParams           = null;  // the parameters to pass to handleURLBarCommand()
  47.  
  48. // suggestion list caching
  49. var acm_lastPrefix  = null;  // the prefix the user had typed in last time we looked
  50.                              // "" denotes blank search string / history dropdown
  51. var acm_lastResults = null;  // cached suggestion result array for this browser window
  52.  
  53. // common protocols and prefixes to ignore
  54. const acm_commonProtocol = /^(http|https|ftp):\/\//i;
  55. const acm_commonPrefix   = /^(www|ftp)\./i;
  56.  
  57. // regular expression for non letters, digits, and spaces
  58. const acm_nonLetterDigitSpace = /[^a-z0-9\s]+/gi;
  59.  
  60. // cached value of the ACM_BOOKMARKS_FIRST preference for better performance when sorting
  61. var acm_bookfirst;
  62.  
  63. // hint to alphaURLCompareFun to check bookmarks-first ordering when called directly, 
  64. // rather than from another sorting function
  65. var acm_checkBookFirst = false;
  66.  
  67. // popup tree
  68. var acm_treePopup = null;
  69. var acm_popupTreePos = null;    
  70. var acm_maxrows;              // number of visible rows in the popup tree
  71. var acm_rowHeight = 0;        // the height of a single row in the popup tree
  72. var acm_suggestions;          // array of AutocompleteCandidate's currently displayed on the popup
  73.  
  74. //var browser_version_index = navigator.userAgent.indexOf("Firefox") + 8;
  75. //var browser_version = navigator.userAgent.substring(browser_version_index, 
  76. //  navigator.userAgent.length);
  77. //if (browser_version.charAt(0) !== '1')
  78.  
  79. // the view for the popup tree
  80. var acm_treePopupView = 
  81. {
  82.   rowCount: 0,
  83.   getCellText: function(row, column)
  84.   {
  85.     var text = "";
  86.   
  87.     // bookmarks separator
  88.     if (row === acm_matching_bookmarks && acm_matching_bookmarks > 0 
  89.         && acm_matching_bookmarks !== acm_suggestions.length)
  90.       return text;
  91.  
  92.     const entry = acm_suggestions[row];
  93.     var date = "[" + acm_micros2date(entry.last_visit) + "]";
  94.  
  95.     if (column.id === "colURL")
  96.       text = entry.URL; 
  97.     else if (column.id === "coltitle")
  98.       text = entry.title;
  99.     else if (column.id === "colsource")
  100.       text = date;
  101.  
  102.     return text; 
  103.   },
  104.   // returns the icon URL to display
  105.   getImageSrc: function(row, column)
  106.   { 
  107.     // bookmarks separator
  108.     if (row === acm_matching_bookmarks && acm_matching_bookmarks > 0 
  109.         && acm_matching_bookmarks !== acm_suggestions.length)
  110.       return null;
  111.  
  112.     const entry =  acm_suggestions[row];
  113.     if (column.index === 0) {
  114.       var iconURL = entry.icon;
  115.       if (iconURL !== "")  // URL-specific favicon
  116.         return iconURL;
  117.       else {  // generic theme favicon
  118.  
  119.         return acm_defaultIcon;
  120.       }
  121.     }
  122.     else 
  123.       return null;
  124.   },
  125.   getRowProperties: function(row, props){ },
  126.   getCellProperties: function(row, column, properties) 
  127.   { 
  128.     // bookmarks separator
  129.    if (row === acm_matching_bookmarks && acm_matching_bookmarks > 0 
  130.         && acm_matching_bookmarks !== acm_suggestions.length)
  131.       return;
  132.  
  133.     // dim the text for all but the first column
  134.     if (column.index !== 0)
  135.       properties.AppendElement(acm_atomService.getAtom("dim")); 
  136.  
  137.     // make the bookmark matches italic
  138.     if (acm_suggestions[row].source === ACM_SOURCE_BOOKMARKS)
  139.       properties.AppendElement(acm_atomService.getAtom("italic"));
  140.  
  141.     // bold the column where the match occurred
  142.     if (acm_getPreference(ACM_BOLD_MATCHING)) {
  143.       var field = acm_suggestions[row].matched_against.substr(2);
  144.       if (column.id === "col" + field)
  145.         properties.AppendElement(acm_atomService.getAtom("bold"));  
  146.     }
  147.   },
  148.   getColumnProperties: function(colid, col, props) { },
  149.   isContainer: function(index) { return false; },
  150.   isEditable: function(row, column) { return false; },
  151.   // draw separator after matching bookmarks, if they are being displayed at the top
  152.   // NOTE: the sanity checks are done elsewhere for better performance, and
  153.   // an extra stub entry is added to acm_suggestions[] after sorting, just for the separator
  154.   isSeparator: function(row) { 
  155.     // do not insert bookmarks separator if either no bookmarks or history entries matched
  156.    if (row === acm_matching_bookmarks && acm_matching_bookmarks > 0 
  157.         && acm_matching_bookmarks !== acm_suggestions.length)
  158.       return true;
  159.     else
  160.       return false;
  161.   },
  162.   isSorted: function(row) { return false; },
  163.   setCellText: function(row, column, value) { },
  164.   setTree: function() { }  
  165. };
  166.  
  167. // timer to defer matching when typing rapidly in the location bar
  168. var acm_typingTimer = Components.classes["@mozilla.org/timer;1"]
  169.                         .createInstance(Components.interfaces.nsITimer);
  170. const acm_timerDelay = 250;  // in milliseconds; 50 is the Firefox default
  171. var acm_timerObserver = 
  172. {
  173.   observe: function(subject, topic, data) {
  174.     // invoke a bit later to (maybe) avoid freezing user's typing on the location bar
  175.     //setTimeout(acm_handleTyping, 30);
  176.     acm_handleTyping();
  177.   }
  178. };
  179.  
  180. // handles a string typed on the location bar by producing the corresponding popup
  181. function acm_handleTyping()
  182. {
  183.   // check for difference in common protocols, e.g. transition from 'http:/' to 'http://'
  184.   // NOTE: do not use indexOf() for regular expressions
  185.   if (acm_lastPrefix !== null
  186.       && acm_lastPrefix.search(acm_commonProtocol) === -1
  187.       && acm_typedPrefix.search(acm_commonProtocol) !== -1)
  188.     acm_lastPrefix = null;  // invalidate window cache
  189.  
  190.   // check for difference in common prefixes, e.g. transition from 'www' to 'www.'
  191.   if (acm_lastPrefix !== null
  192.       && acm_lastPrefix.search(acm_commonPrefix) === -1
  193.       && acm_typedPrefix.search(acm_commonPrefix) !== -1)
  194.     acm_lastPrefix = null;  // invalidate window cache
  195.  
  196.   // for the blank search string, Firefox uses the order by which entries were entered 
  197.   // in the history file; we use most recently visited.
  198.   if (acm_typedPrefix === "") {
  199.     // set the number of matching bookmarks for placing the bookmarks separator on the popup
  200.     if (acm_getPreference(ACM_MATCH_BOOKMARKS) && acm_getPreference(ACM_BOOKMARKS_FIRST))
  201.       acm_matching_bookmarks = acm_Aggregator.singleton().bookmarks.length;
  202.     else
  203.       acm_matching_bookmarks = -1;
  204.     acm_buildPopup(acm_Aggregator.singleton().allCandidates.sort(acm_mruCompareFun));
  205.   }
  206.  
  207.   // check if we can reuse the last computed results; no need to re-sort
  208.   else if (acm_lastPrefix !== null && acm_lastPrefix !== "" 
  209.            && acm_typedPrefix.length >= acm_lastPrefix.length
  210.            && acm_typedPrefix.substring(0, acm_lastPrefix.length) === acm_lastPrefix) {
  211.     // remove bookmarks separator, as it will be re-inserted by acm_buildPopup()
  212.     if (acm_matching_bookmarks > 0 && acm_matching_bookmarks !== acm_suggestions.length)
  213.       acm_lastResults.splice(acm_matching_bookmarks, 1);
  214.     acm_lastResults = acm_getFilteredCandidates(acm_lastResults);
  215.     acm_buildPopup(acm_lastResults);
  216.   } 
  217.  
  218.   // perform the matching from scratch
  219.   else {
  220.     acm_lastResults = acm_getSortedSuggestionList(null);
  221.     acm_buildPopup(acm_lastResults);
  222.   }
  223.  
  224.   // update prefix
  225.   acm_lastPrefix = acm_typedPrefix;
  226.  
  227.   // complete and highlight best match inline, it is matched at the beginning of URL or title 
  228.   if (acm_getPreference(ACM_INLINE) && acm_popupExists 
  229.       && acm_suggestions[0].matched_against.indexOf("b-") === 0 && !acm_disableInlining) {
  230.     // strip common protocols and prefixes, unless they were typed in
  231.     var field = acm_suggestions[0].matched_against.substr(2);
  232.     var new_value = acm_suggestions[0][field];
  233.     if (field === "URL") {
  234.       if (acm_typedPrefix.search(acm_commonProtocol) === -1)
  235.         new_value = new_value.replace(acm_commonProtocol, "");
  236.       if (acm_typedPrefix.search(acm_commonPrefix) === -1)
  237.         new_value = new_value.replace(acm_commonPrefix, "");
  238.     }
  239.     const urlbar = document.getElementById("urlbar");
  240.     urlbar.value = new_value;
  241.     urlbar.setSelectionRange(acm_typedPrefix.length, urlbar.textLength);  
  242.   }
  243. }
  244.  
  245. // for debugging output
  246. function acm_console(msg) {
  247.   const consoleService = Components.classes["@mozilla.org/consoleservice;1"]
  248.                                .getService(Components.interfaces.nsIConsoleService);
  249.   consoleService.logStringMessage(msg);
  250. }
  251.  
  252. // invalidates the caches of all the main browser windows; needed because 
  253. // every window maintains its own instances of the acm_* global variables
  254. function acm_invalidateCaches()
  255. {
  256.   var browser_windows = acm_mediator.getEnumerator("navigator:browser");
  257.   while (browser_windows.hasMoreElements()) 
  258.     browser_windows.getNext().acm_lastPrefix = null;
  259. }
  260.  
  261. // handles text being typed in the location bar
  262. function acm_urlbarOnInput(event)
  263. {
  264.   // do not display popup if Autocomplete is off 
  265.   if (acm_getPreference(ACM_ACTIVE_COMPONENT) === "off")
  266.     return;
  267.  
  268.   // popup has been temporarily disabled
  269.   if (acm_disablePopup) 
  270.     return;
  271.  
  272.   // prevent re-display of popup when deleting an entry or for a clickngo middle click on a new tab
  273.   if (acm_preventPopup) {
  274.     acm_preventPopup = false;
  275.     return;
  276.   } 
  277.  
  278.   // enable inlining if characters have been added to the prefix
  279.   const urlbar = document.getElementById("urlbar");
  280.   if (acm_typedPrefix !== null && urlbar.value.length > acm_typedPrefix.length && acm_disableInlining) 
  281.     acm_disableInlining = false;
  282.  
  283.   var lastPrefix = acm_typedPrefix;
  284.   acm_typedPrefix = urlbar.value;
  285.  
  286.   if (acm_getPreference(ACM_SHOW_ONARROW) && !acm_popupExists)
  287.     return;
  288.  
  289.   // the urlbar is filled with the most recently loaded URL (possible blank) when Esc is pressed on 
  290.   // the popup in a full-screen Open Web Location dialog;
  291.   // restore acm_typedPrefix to its previous value, and do not set acm_popupExists to false, 
  292.   // since that will be done in the Esc handler
  293.   if (!document.getElementById("urlbar-container")) {
  294.     var lastURL = acm_prefs.getComplexValue("general.open_location.last_url", 
  295.                                             Components.interfaces.nsISupportsString).data;  
  296.     if (lastPrefix !== null && acm_typedPrefix === lastURL) {
  297.       acm_typedPrefix = lastPrefix;
  298.       return;
  299.     }
  300.   }
  301.  
  302.   // hide popup if location bar was cleared
  303.   if (acm_typedPrefix === null || acm_typedPrefix === "") {
  304. //      || acm_typedPrefix === window._content.document.location.href) {
  305.  
  306.     acm_typingTimer.cancel();
  307.     if (acm_popupExists) {
  308.       acm_popupExists = false;
  309.       const popup = document.getElementById("ACM_Popup");
  310.       popup.hidePopup();
  311.     }
  312.     return;
  313.   } 
  314.  
  315.   // restart typing timer
  316.   acm_typingTimer.cancel(); 
  317.   acm_typingTimer.init(acm_timerObserver, acm_timerDelay, acm_typingTimer.TYPE_ONE_SHOT);
  318. }
  319.  
  320. // handles keyboard navigation on the location bar and the popup
  321. function acm_urlbarNavigation(event)
  322. {
  323.  
  324.   const urlbar = document.getElementById("urlbar");
  325.   const popup  = document.getElementById("ACM_Popup");
  326.   var changePos;
  327.  
  328.   if (event.altKey) {
  329.     var key = event.charCode;
  330.  
  331.     // Alt+L places the focus on the location bar, temporarily disabling the popup
  332.     // HACK: for the Open Web Location dialog, the wrong char code is given to us!?
  333.     if ( (document.getElementById("urlbar-container") && key === event.DOM_VK_L) 
  334.        || (document.getElementById("openLocation") &&key === event.DOM_VK_SEPARATOR) ) {
  335.       acm_typingTimer.cancel();
  336.       if (acm_popupExists) {
  337.         acm_popupExists = false;
  338.         popup.hidePopup();        
  339.       }
  340.       urlbar.select();
  341.       acm_disablePopup = true;
  342.       event.preventDefault();
  343.       event.stopPropagation();
  344.       return;
  345.     }
  346.   }
  347.  
  348.   // only for the main location bar
  349.   if (event.ctrlKey  && document.getElementById("urlbar-container")) {
  350.     var key = event.charCode; 
  351.  
  352.     // Ctrl+<number> re-sorts the suggestion list according to the corresponding criterion
  353.     if (acm_popupExists && key >= event.DOM_VK_0 && key <= event.DOM_VK_9) {  
  354.       // remove bookmarks separator, as it is going to be re-inserted by acm_buildPopup()
  355.      if (acm_matching_bookmarks > 0 && acm_matching_bookmarks !== acm_suggestions.length)
  356.         acm_lastResults.splice(acm_matching_bookmarks, 1);
  357.  
  358.       switch (key) {
  359.         case event.DOM_VK_1: 
  360.           acm_showTooltip(0);
  361.           acm_buildPopup(acm_lastResults.sort(acm_defaultCompareFun));
  362.           break;
  363.         case event.DOM_VK_2: 
  364.           acm_showTooltip(1);
  365.           acm_buildPopup(acm_lastResults.sort(acm_topCompareFun));
  366.           break;
  367.         case event.DOM_VK_3: 
  368.           acm_showTooltip(2);
  369.           acm_buildPopup(acm_lastResults.sort(acm_mfuCompareFun));
  370.           break;
  371.         case event.DOM_VK_4: 
  372.           acm_showTooltip(3);
  373.           acm_buildPopup(acm_lastResults.sort(acm_mruCompareFun));
  374.           break;
  375.         case event.DOM_VK_5: 
  376.           acm_showTooltip(4);
  377.           acm_buildPopup(acm_lastResults.sort(acm_alphaURLCompareFun));
  378.           break; 
  379.         case event.DOM_VK_6: 
  380.           acm_showTooltip(5);
  381.           acm_buildPopup(acm_lastResults.sort(acm_alphaTitleCompareFun));
  382.           break; 
  383.         default:
  384.           // restore the bookmarks separator, which we removed before re-sorting
  385.           if (acm_matching_bookmarks > 0 && acm_matching_bookmarks !== acm_suggestions.length)
  386.             acm_lastResults.splice(acm_matching_bookmarks, 0, new AutocompleteCandidate());
  387.           break;
  388.       }
  389.  
  390.       // prevent other elements from handling this event (necessary for Windows only!)
  391.       event.preventDefault();
  392.       event.stopPropagation();
  393.  
  394.       return;
  395.     }
  396.   }
  397.  
  398.   if ( (event.keyCode === event.DOM_VK_TAB) && acm_popupExists ) {
  399.     changePos = (event.shiftKey) ? -1 : 1;
  400.  
  401.     // Tab completion; doesn't work yet!
  402. /*    if (!event.shiftKey) {
  403.       // we are on the location bar; fill in the longest common prefix of all suggestions' URLs
  404.       if (acm_popupTreePos === -1) {
  405.         var lcp = acm_longestCommonPrefix(acm_suggestions);
  406.  
  407.       }      
  408.       else {  // we are on the popup; filter suggestions based on current location bar value
  409.  
  410.         //for (var i = acm_suggestions.length - 1; i >= 0; i--) {
  411.         //  if (acm_suggestions[i])
  412.         //    acm_suggestions.splice(i, 1);     
  413.         //}
  414.       }
  415.     }
  416.     // undo last action
  417.     else {
  418.  
  419.     } */
  420.  
  421.     // prevent other elements from handling this event
  422.     event.preventDefault();
  423.     event.stopPropagation();
  424.   } 
  425.  
  426.   // Del or Shift+Del deletes an entry
  427.   else if (event.keyCode === event.DOM_VK_DELETE && acm_popupExists 
  428.            && acm_popupTreePos > -1) {
  429.     // ignore if it's a bookmarks separator
  430.     if (acm_popupTreePos === acm_matching_bookmarks && acm_matching_bookmarks > 0 
  431.         && acm_matching_bookmarks !== acm_suggestions.length) {
  432.       event.preventDefault();
  433.       event.stopPropagation();
  434.       return;
  435.     }
  436.  
  437.     // if it is a history entry, delete it from the history file
  438.     if (acm_suggestions[acm_popupTreePos].source === ACM_SOURCE_HISTORY) {
  439.       var uriToDelete = acm_ioService.newURI(acm_suggestions[acm_popupTreePos].URL, null, null);
  440.       acm_browserHistory.removePage(uriToDelete); 
  441.     }
  442.     // if it is a bookmark, just remove it from the candidates array
  443.     else if (acm_suggestions[acm_popupTreePos].source === ACM_SOURCE_BOOKMARKS) 
  444.       acm_Aggregator.singleton().removeSingleBookmark(acm_suggestions[acm_popupTreePos].resource, 
  445.         acm_suggestions[acm_popupTreePos].URL); 
  446.  
  447.     // find what the new active suggestion should be after the reconstruction of the popup
  448.     var newSelection = (acm_popupTreePos < acm_suggestions.length - 1) ? 
  449.       acm_popupTreePos : acm_suggestions.length - 2;
  450.  
  451.     if (newSelection === -1) { // no suggestions left, close popup
  452.       acm_popupExists = false;
  453.       popup.hidePopup();
  454.       return;
  455.     }
  456.  
  457.     // was a bookmark removed?
  458.     var bookmarkRemoved = false;
  459.     if (acm_matching_bookmarks > 0
  460.         && acm_suggestions[acm_popupTreePos].source === ACM_SOURCE_BOOKMARKS)
  461.       bookmarkRemoved = true;
  462.  
  463.     // remove bookmarks separator, as it is going to be re-inserted by acm_buildPopup()
  464.     if (acm_matching_bookmarks > 0 && acm_matching_bookmarks !== acm_suggestions.length)
  465.       acm_suggestions.splice(acm_matching_bookmarks, 1);
  466.  
  467.     // decrement acm_matching_bookmarks if needed
  468.     if (bookmarkRemoved && acm_matching_bookmarks > 0)
  469.       acm_matching_bookmarks--;
  470.  
  471.     // remove selected entry (remember, we might have removed the bookmarks separator already)
  472.     acm_popupTreePos = (bookmarkRemoved || acm_matching_bookmarks < 1) ? 
  473.                         acm_popupTreePos : acm_popupTreePos - 1;
  474.     acm_suggestions.splice(acm_popupTreePos, 1);
  475.  
  476.     // update cached results and redraw the popup, even if we haven't removed an entry
  477.     // NOTE: acm_buildPopup() resets acm_popupTreePos, so we have to restore it for acm_showPopup()
  478.     acm_lastResults = acm_suggestions;  
  479.     acm_buildPopup(acm_lastResults);
  480.     acm_popupTreePos = newSelection;
  481.  
  482.     // changing urlbar.value triggers 'oninput' on Windows (and in Firefox 1.0)
  483.     // NOTE: always do this after invoking acm_buildPopup() 
  484.     acm_preventPopup = true;  
  485.  
  486.     if (acm_popupTreePos === acm_matching_bookmarks && acm_matching_bookmarks > 0 
  487.         && acm_matching_bookmarks !== acm_suggestions.length)
  488.       urlbar.value = acm_typedPrefix;
  489.     else 
  490.       urlbar.value = acm_suggestions[acm_popupTreePos].URL;  
  491.  
  492.     return;
  493.   }
  494.  
  495.   // Alt+Down, Alt+Up, or F4 toggle the history popup; Ctrl+Up/Down switches tabs
  496.   else if (event.keyCode === event.DOM_VK_DOWN) {
  497.     if (event.altKey || event.ctrlKey)
  498.       return;
  499.  
  500.     // re-enable popup if it has been temporarily disabled
  501.     acm_disablePopup = false;
  502.  
  503.     changePos = 1;
  504.   }
  505.  
  506.   else if (event.keyCode === event.DOM_VK_UP) {
  507.     if (event.altKey || event.ctrlKey)
  508.       return;
  509.  
  510.     changePos = -1;
  511.   }
  512.  
  513.   // full screen mode; fix for bug 228355
  514.   else if (event.keyCode === event.DOM_VK_F11) {
  515.     if (acm_popupExists) {
  516.       acm_popupExists = false;
  517.       popup.hidePopup();
  518.     } 
  519.     return;
  520.   }
  521.  
  522.   // close popup for Ctrl+PgUp/PgDown (switching tabs)
  523.   else if (event.keyCode === event.DOM_VK_PAGE_DOWN) {
  524.     if (event.ctrlKey) {
  525.       if (acm_popupExists) {
  526.         acm_popupExists = false;
  527.         popup.hidePopup();
  528.       }
  529.       return;
  530.     }
  531.     else {
  532.       if (acm_popupTreePos !== -1)
  533.         changePos = acm_maxrows - 1;
  534.       // move to first suggestion from location bar
  535.       else
  536.         changePos = 1;
  537.     }
  538.   }
  539.  
  540.   else if (event.keyCode === event.DOM_VK_PAGE_UP) {
  541.     if (event.ctrlKey) {
  542.       if (acm_popupExists) {
  543.         acm_popupExists = false;
  544.         popup.hidePopup();
  545.       }
  546.       return;
  547.     }
  548.     else {
  549.       if (acm_popupTreePos !== -1)
  550.         changePos = -(acm_maxrows - 1);
  551.       // move to last suggestion from location bar
  552.       else
  553.         changePos = acm_suggestions.length;
  554.     }
  555.   }
  556.  
  557.   // delete possibly inlined URL, but do not close the popup
  558.   // NOTE: the use of Ctrl apparently changes the char codes!?
  559.   else if (event.keyCode === event.DOM_VK_DELETE || event.keyCode === event.DOM_VK_BACK_SPACE
  560.            || (event.ctrlKey && event.charCode - 32 === event.DOM_VK_X) ) {
  561.     if (acm_popupExists) {
  562.       // cursor is on the location bar
  563.       if (acm_popupTreePos === -1) {
  564.         if (acm_getPreference(ACM_INLINE) && acm_suggestions[0].matched_against.indexOf("b-") === 0
  565.             && !acm_disableInlining) {
  566.           // fix for bug 135019
  567.           urlbar.value = urlbar.value.substring(0, urlbar.selectionStart);
  568.       acm_disableInlining = true;
  569.         }
  570.         else
  571.           urlbar.value = acm_typedPrefix;
  572.       }
  573.       // cursor is somewhere on the popup; no part of the URL is selected
  574.       else {
  575.         urlbar.value = acm_suggestions[acm_popupTreePos].URL;
  576.         if (acm_getPreference(ACM_INLINE) && !acm_disableInlining) {
  577.           acm_disableInlining = true;
  578.           acm_typedPrefix = urlbar.value;
  579.         }
  580.       }
  581.     }
  582.     // deleting a part of the URL when there is no popup shouldn't trigger inline autocomplete
  583.     else { 
  584.       if (acm_getPreference(ACM_INLINE) && !acm_disableInlining) {
  585.         acm_disableInlining = true;
  586.         acm_typedPrefix = urlbar.value;
  587.       }
  588.     }
  589.  
  590.     return;
  591.   }
  592.  
  593.   // close the popup, inlining the selected address, but do not load the page
  594.   else if (event.keyCode === event.DOM_VK_LEFT 
  595.            || event.keyCode === event.DOM_VK_RIGHT
  596.            || event.keyCode === event.DOM_VK_HOME 
  597.            || event.keyCode === event.DOM_VK_END) {
  598.     // cancel the timer, so that the popup does not appear 
  599.     acm_typingTimer.cancel();
  600.  
  601.     if (acm_popupExists) {
  602.       // cursor is on the location bar
  603.       if (acm_popupTreePos === -1) {
  604.         if (acm_getPreference(ACM_INLINE) && acm_suggestions[0].matched_against.indexOf("b-") === 0
  605.             && !acm_disableInlining) 
  606.           urlbar.value = acm_suggestions[0][acm_suggestions[0].matched_against.substr(2)];
  607.         else 
  608.           urlbar.value = acm_typedPrefix;
  609.       }
  610.       // cursor is somewhere on the popup
  611.       else 
  612.         urlbar.value = acm_suggestions[acm_popupTreePos].URL;
  613.  
  614.       acm_popupExists = false;
  615.       popup.hidePopup();
  616.     }    
  617.     return;
  618.   }
  619.  
  620.   else if (event.keyCode === event.DOM_VK_ESCAPE) {
  621.     // cancel the timer, so that the popup does not appear after Esc is pressed
  622.     acm_typingTimer.cancel();
  623.  
  624.     if (acm_popupExists) {
  625.       urlbar.value = acm_typedPrefix;
  626.       acm_popupExists = false;
  627.       popup.hidePopup();
  628.  
  629.       // prevent other elements from handling this event
  630.       event.preventDefault();
  631.       event.stopPropagation();
  632.     }
  633.     else {
  634.       urlbar.setAttribute("popupOpen", "false"); 
  635.  
  636.       if (document.getElementById("urlbar-container"))
  637.         handleURLBarRevert();
  638.     }
  639.  
  640.     // re-enable popup if it has been temporarily disabled
  641.     acm_disablePopup = false;
  642.  
  643.     return;
  644.   } 
  645.  
  646.   // runs after the ontextentered handler
  647.   else if (event.keyCode === event.DOM_VK_ENTER || event.keyCode === event.DOM_VK_RETURN) {
  648.     // Enter on the bookmarks separator
  649.     // NOTE: if the popup is off, acm_matching_bookmarks retains its value from last time
  650.     if (acm_popupExists
  651.         && acm_treePopup.view.selection.currentIndex === acm_matching_bookmarks 
  652.         && acm_matching_bookmarks > 0 && acm_matching_bookmarks !== acm_suggestions.length)
  653.       return;
  654.  
  655.     // fix for bug 101497, if text in location bar is not fully selected, e.g., after a Ctrl+L
  656.     // NOTE: don't move this down!
  657.     if (acm_getPreference(ACM_INLINE) && urlbar.selectionStart !== urlbar.selectionEnd
  658.         && urlbar.selectionStart !== 0) 
  659.       urlbar.value = acm_suggestions[0].URL;
  660.  
  661.     // cancel the timer, so that the popup does not appear after Enter is pressed
  662.     acm_typingTimer.cancel();
  663.  
  664.     // hide the popup
  665.     var popupExisted = false;
  666.     if (acm_popupExists) {
  667.       popupExisted = true;
  668.       acm_popupExists = false;
  669.       popup.hidePopup();
  670.     }
  671.  
  672.     // re-enable popup if it has been temporarily disabled
  673.     acm_disablePopup = false;
  674.  
  675.     // non-browser location bar: if the popup was open, inline the URL but do not close the 
  676.     // dialog, otherwise load the existing URL
  677.     if (!document.getElementById("urlbar-container")) {
  678.       if (popupExisted) {
  679.         event.preventDefault();
  680.         event.stopPropagation();
  681.       }
  682.       return;
  683.     }
  684.  
  685.     // load the URL
  686.     // NOTE: other extensions (e.g., Tabbrowser Preferences, DeviantLink) may define 
  687.     // their own custom 'ontextentered' handler, which may itself have a 'param' argument; 
  688.     // replace all such arguments with 'acm_loadParams' and then invoke the handler
  689.     var originalHandler = acm_ontextenteredHandler.replace("param", "acm_loadParams", "g");
  690.     // NOTE: no 'return' is allowed in eval()
  691.     originalHandler = originalHandler.replace("return ", "");  
  692.     eval(originalHandler);
  693.     return;
  694.   }
  695.  
  696.   // return for most other keystrokes
  697.   else {
  698.     // hide popup on arbitrary keyboard shortcut
  699.     if (event.shiftKey || event.ctrlKey || event.altKey 
  700.         || (event.keyCode >= event.DOM_VK_F1 && event.keyCode <= event.DOM_VK_F24
  701.         && event.keyCode !== event.DOM_VK_F4)  // F4 needs acm_popupExists to be true
  702.         && acm_popupExists) {
  703.       // do not close popup for common Shift combinations for special characters 
  704.       // than can appear on the location bar
  705.       var key = event.charCode;  
  706.       if ( (key >= 33 && key <= 38) || (key >= 40 && key <= 43) || key === 58 || key === 60
  707.            || (key >= 62 && key <= 64) || key === 94 || key === 95 || (key >= 123 && key <= 126) ) 
  708.         return;
  709.  
  710.       // for extra French keyboard special characters
  711.       if (key === 46 || key === 47 || (key >=  51 && key <= 56) || key === 163 || key === 176)
  712.         return;
  713.       
  714.       // NOTE: The use of Shift apparently changes the char codes!?
  715.       key -= 32; 
  716.  
  717.       // fix for bug 91536; see https://bugzilla.mozilla.org/show_bug.cgi?id=91536#c4
  718.       if ( (key === event.DOM_VK_X && urlbar.selectionStart === 0 
  719.             && urlbar.selectionEnd === urlbar.textLength) 
  720.            || (key !== event.DOM_VK_A && key !== event.DOM_VK_C && key !== event.DOM_VK_L
  721.                && key !== event.DOM_VK_X && key !== event.DOM_VK_Z) ) {
  722.           acm_popupExists = false; 
  723.           popup.hidePopup();
  724.       }
  725.     } 
  726.     return;
  727.   }
  728.  
  729.   // return if Autocomplete is off
  730.   if (acm_getPreference(ACM_ACTIVE_COMPONENT) === "off")
  731.     return;
  732.  
  733.   // if there is no popup yet, build and display it; 
  734.   // this executes only for arrow up/down and Page Up/Down 
  735.   if (!acm_popupExists) {
  736.     acm_typedPrefix = acm_lastPrefix = urlbar.value;
  737.  
  738.     // for the blank search string, Firefox uses the order by which entries were entered 
  739.     // in the history file; we use most recently visited.
  740.     if (acm_typedPrefix === "") {
  741.       // set the number of matching bookmarks for placing the bookmarks separator on the popup
  742.       if (acm_getPreference(ACM_MATCH_BOOKMARKS) && acm_getPreference(ACM_BOOKMARKS_FIRST))
  743.         acm_matching_bookmarks = acm_Aggregator.singleton().bookmarks.length;
  744.       else
  745.         acm_matching_bookmarks = -1;
  746.  
  747.       acm_buildPopup(acm_Aggregator.singleton().allCandidates.sort(acm_mruCompareFun));
  748.     }
  749.     else {
  750.       acm_lastResults = acm_getSortedSuggestionList(null);
  751.       acm_buildPopup(acm_lastResults); 
  752.     }
  753.     return;
  754.   }   
  755.  
  756.   // deselect text in the location bar since we are on the popup
  757.   if (acm_getPreference(ACM_INLINE))
  758.     urlbar.setSelectionRange(urlbar.textLength, urlbar.textLength); 
  759.  
  760.   // update tree selection; -1 indicates we are on the location bar
  761.   // NOTE: PgUp/PgDown from location bar is handled in the respective keyboard handlers
  762.   // HOWTOFIX: when we Page Down on the popup, the right end of a long URL is displayed
  763.   var newTreePos = acm_popupTreePos + changePos;  
  764.   if (newTreePos < 0) {
  765.     // page up on the first page; move to first suggestion
  766.     if (acm_popupTreePos >= 1 && acm_popupTreePos <= acm_maxrows - 1) 
  767.       newTreePos = 0;
  768.     // page up from first suggestion; move to location bar
  769.     else if (acm_popupTreePos === 0)
  770.       newTreePos = -1; 
  771.     // arrow up from location bar
  772.     else 
  773.       newTreePos = acm_suggestions.length - 1;
  774.   } 
  775.  
  776.   else if (newTreePos > acm_suggestions.length - 1) {
  777.     // page down on the last page; move to last suggestion
  778.     if (acm_popupTreePos >= acm_suggestions.length - acm_maxrows - 1 
  779.         && acm_popupTreePos <= acm_suggestions.length - 2) 
  780.       newTreePos = acm_suggestions.length - 1;
  781.     // page down from last suggestion; move to location bar
  782.     else 
  783.       newTreePos = -1; 
  784.   }
  785.  
  786.   // otherwise, do nothing
  787.   //else
  788.   //  ;
  789.  
  790.   // move to new position on tree
  791.   if (newTreePos === -1) {
  792.     acm_treePopup.view.selection.clearSelection();
  793.     acm_treePopup.treeBoxObject.scrollToRow(0);
  794.   }
  795.   else {
  796.     acm_treePopup.view.selection.select(newTreePos);
  797.     acm_treePopup.treeBoxObject.ensureRowIsVisible(newTreePos);
  798.   }
  799.   acm_popupTreePos = newTreePos;
  800.  
  801.   // update location bar value
  802.   if ( (newTreePos === -1) 
  803.        || (newTreePos === acm_matching_bookmarks && acm_matching_bookmarks > 0 
  804.            && acm_matching_bookmarks !== acm_suggestions.length) )
  805.     urlbar.value = acm_typedPrefix;
  806.   else 
  807.     urlbar.value = acm_suggestions[newTreePos].URL;  
  808. }
  809.  
  810. // re-builds and displays the popup, populated with entries from the argument sorted suggestion list
  811. function acm_buildPopup(sortedList)
  812. {
  813.   const urlbar = document.getElementById("urlbar");
  814.   const popup  = document.getElementById("ACM_Popup");
  815.  
  816.   // if no entries matched, hide popup and return
  817.   // NOTE: this is also the case when Autocomplete is turned off at browser startup
  818.   if (sortedList.length === 0) {
  819.     if (acm_popupExists) {
  820.       acm_popupExists = false;
  821.       popup.hidePopup();
  822.     }
  823.     return;
  824.   }
  825.  
  826.   // construct the tree for the popup
  827.   acm_suggestions = sortedList;
  828.  
  829.   // add a stub entry for the bookmarks separator if at least one bookmark matched
  830.   if (acm_getPreference(ACM_MATCH_BOOKMARKS) && acm_getPreference(ACM_BOOKMARKS_FIRST)
  831.       && acm_matching_bookmarks > 0 && acm_matching_bookmarks !== acm_suggestions.length) 
  832.     acm_suggestions.splice(acm_matching_bookmarks, 0, new AutocompleteCandidate());
  833.  
  834.   if (!acm_treePopup)
  835.     acm_treePopup = document.getAnonymousElementByAttribute(popup, "anonid", "tree");
  836.   else {
  837.     // used when deleting a suggestion to re-center the popup
  838.     var firstVisibleRow = acm_treePopup.treeBoxObject.getFirstVisibleRow();
  839.     acm_treePopup.treeBoxObject.invalidate();  
  840.   }
  841.  
  842.   acm_treePopupView.rowCount = acm_suggestions.length;
  843.   acm_treePopup.treeBoxObject.view = acm_treePopupView;
  844.  
  845.   // NOTE: this doesn't work, so set the 'height' property instead
  846.   //acm_treePopup.setAttribute("rows", visible_rows);
  847.  
  848.   // calculate the number of visible rows before restoring acm_maxrows
  849.   var visible_rows = (acm_suggestions.length > acm_maxrows) ? acm_maxrows : acm_suggestions.length;
  850.  
  851.   // show and hide the popup once to get the row height
  852.   if (!acm_rowHeight) {
  853.     popup.showPopup(urlbar, -1, -1, "popup", "bottomleft", "topleft");
  854.     acm_rowHeight = acm_treePopup.treeBoxObject.rowHeight;
  855.     popup.hidePopup();  // restores acm_maxrows and adds 'keypress' event listener 
  856.   }
  857.  
  858.   // temporarily disable keyboard tab selection, since it conflicts with on-the-fly re-sorting
  859.   // on Windows; will re-enable it when the popup is hidden
  860.   // NOTE: this is defined in browser.js, not other places we support (e.g., Open Web Location dialog)
  861.   if (document.getElementById("urlbar-container"))
  862.     window.removeEventListener("keypress", ctrlNumberTabSelection, false); 
  863.  
  864.   // set popup height and scrollbar
  865.  
  866.   acm_treePopup.setAttribute("height", visible_rows * acm_rowHeight);       
  867.   acm_treePopup.setAttribute("hidescrollbar", acm_suggestions.length <= acm_maxrows);
  868.   acm_popupTreePos = -1;  // we are on the location bar
  869.  
  870.   // determine column order
  871.   // NOTE: do not use columns[], since the tree does not exist yet
  872.   var show_titles  = acm_getPreference(ACM_SHOW_TITLES);
  873.   var swap_columns = acm_getPreference(ACM_SWAP_COLUMNS);
  874.   var colURL, colTitle;
  875.  
  876.   if (show_titles && swap_columns) {
  877.     // we are in the initial state; swap columns
  878.     if (acm_treePopup.firstChild.childNodes[0].getAttribute("id") === "colURL") {
  879.       colURL   = acm_treePopup.firstChild.childNodes[0];
  880.       colTitle = acm_treePopup.firstChild.childNodes[1];
  881.       acm_treePopup.firstChild.insertBefore(colTitle, colURL);
  882.     }
  883.     // columns have been swapped already; leave it that way
  884.     else {
  885.       colURL   = acm_treePopup.firstChild.childNodes[1];
  886.       colTitle = acm_treePopup.firstChild.childNodes[0];  
  887.     }
  888.   }
  889.   else {
  890.     // we are in the initial state; leave it that way
  891.     if (acm_treePopup.firstChild.childNodes[0].getAttribute("id") === "colURL") {
  892.       colURL   = acm_treePopup.firstChild.childNodes[0];
  893.       colTitle = acm_treePopup.firstChild.childNodes[1];
  894.     }
  895.     // restore initial state
  896.     else {
  897.       colURL   = acm_treePopup.firstChild.childNodes[1];
  898.       colTitle = acm_treePopup.firstChild.childNodes[0];
  899.       acm_treePopup.firstChild.insertBefore(colURL, colTitle);
  900.     }
  901.   }
  902.  
  903.   // determine the visible columns and their crop modes
  904.   colURL.setAttribute("crop", acm_getPreference(ACM_ADDRESS_TRUNC));
  905.  
  906.   if (show_titles) {
  907.     colTitle.setAttribute("crop", acm_getPreference(ACM_TITLE_TRUNC));
  908.     colTitle.setAttribute("hidden", "false");
  909.   }
  910.   else 
  911.     colTitle.setAttribute("hidden", "true");
  912.  
  913.   var colSource = acm_treePopup.firstChild.childNodes[2];
  914.   if (acm_getPreference(ACM_SHOW_DATE))
  915.     colSource.setAttribute("hidden", "false");
  916.   else 
  917.     colSource.setAttribute("hidden", "true"); 
  918.  
  919.   // set column widths heuristically
  920.   // NOTE: if these are set in acpopup.xml, they don't adapt dynamically to a column swap
  921.   colURL.setAttribute("flex", 12);
  922.   colTitle.setAttribute("flex", 6);
  923.   colSource.setAttribute("flex", 5);
  924.  
  925.   // show popup; workaround for bug 348146 that only appears on Mac OS X
  926.   // NOTE: set this to 'true' to make inline autocompletion work the first time the popup 
  927.   // appears, and also so that the popup is not shown after buttons have been pressed
  928.   acm_popupExists = true; 
  929.   setTimeout(acm_showPopup, 0, firstVisibleRow);
  930. }
  931.  
  932. // shows the popup; firstVisibleRow is used when deleting a suggestion to re-center the popup
  933. function acm_showPopup(firstVisibleRow)
  934. {
  935.   // needed due to the async nature (setTimeout() call) of the popup creation 
  936.   if (!acm_popupExists)
  937.     return;
  938.  
  939.   const urlbar = document.getElementById("urlbar");
  940.   const popup  = document.getElementById("ACM_Popup");
  941.   popup.showPopup(urlbar, -1, -1, "popup", "bottomleft", "topleft");
  942.  
  943.   if (acm_popupTreePos === -1) {  // new popup
  944.     acm_treePopup.view.selection.select(-1); 
  945.     acm_treePopup.treeBoxObject.scrollToRow(0);
  946.   }
  947.   else {  // after deleting an entry
  948.     acm_treePopup.view.selection.select(acm_popupTreePos);
  949.  
  950.     // NOTE: acm_treePopup.treeBoxObject.getLastVisibleRow() returns acm_suggestions.length + 1
  951.     // on the last popup page (if that page is not also the first)!?
  952.     if (acm_popupTreePos >= acm_suggestions.length - acm_maxrows + 1
  953.         && acm_popupTreePos <= acm_suggestions.length - 1
  954.         && firstVisibleRow !== 0) 
  955.       acm_treePopup.treeBoxObject.scrollToRow(firstVisibleRow - 1);
  956.     else
  957.       acm_treePopup.treeBoxObject.scrollToRow(firstVisibleRow);
  958.   }
  959. }
  960.  
  961. // shows the urlbar tooltip
  962. function acm_showTooltip(tooltipIndex)
  963.   // another tooltip is being displayed
  964.   if (acm_tooltipTimeout !== null) {
  965.     acm_tooltip.setAttribute("hidden", true);    
  966.     clearTimeout(acm_tooltipTimeout);
  967.   }
  968.  
  969.   // the tooltip labels are stored in a deck
  970.   acm_tooltip = document.getElementById("acm_resorting-deck").childNodes.item(tooltipIndex);
  971.   acm_tooltip.setAttribute("hidden", false);
  972.   acm_tooltipTimeout = setTimeout(acm_hideTooltip, 3000);
  973. }
  974.  
  975. // hides the urlbar tooltip (after a timeout set in acm_showTooltip())
  976. function acm_hideTooltip()
  977.   acm_tooltip.setAttribute("hidden", true);
  978.   acm_tooltipTimeout = null;  
  979. }
  980.  
  981. // toggles history dropdown; triggered by Alt+Up, Alt+Down, or F4 on the location bar.
  982. // Firefox uses the order by which entries were entered in the history file,
  983. // we use most recently visited.
  984. function acm_toggleHistoryPopup()
  985. {
  986.   // return if Autocomplete is off
  987.   if (acm_getPreference(ACM_ACTIVE_COMPONENT) === "off") 
  988.     return;
  989.  
  990.   if (!acm_popupExists) {
  991.     if (acm_typingTimer)
  992.       acm_typingTimer.cancel();
  993.  
  994.     // is restored to its regular value on 'popuphidden'  
  995.     acm_maxrows = document.getElementById("urlbar").maxDropMarkerRows;  
  996.  
  997.     // set the number of matching bookmarks for placing the bookmarks separator on the popup
  998.     if (acm_getPreference(ACM_MATCH_BOOKMARKS) && acm_getPreference(ACM_BOOKMARKS_FIRST))
  999.       acm_matching_bookmarks = acm_Aggregator.singleton().bookmarks.length;
  1000.     else
  1001.       acm_matching_bookmarks = -1;
  1002.  
  1003.     acm_buildPopup(acm_Aggregator.singleton().allCandidates.sort(acm_mruCompareFun));
  1004.     acm_lastPrefix = "";  // invalidate window cache
  1005.   }
  1006.   else {
  1007.     acm_popupExists = false;
  1008.     const popup = document.getElementById("ACM_Popup");
  1009.     popup.hidePopup();
  1010.   }
  1011. }
  1012.  
  1013. // handles mouse clicks on popup items
  1014. // NOTE: mouse selection/navigation happens separately from keyboard selection/navigation
  1015. function acm_popupMouseClick(event, popup)
  1016. {
  1017.   // mouse click on the bookmarks separator
  1018.   if (acm_treePopup.view.selection.currentIndex === acm_matching_bookmarks
  1019.       && acm_matching_bookmarks > 0 && acm_matching_bookmarks !== acm_suggestions.length)
  1020.     return;
  1021.  
  1022.   // on the browser location bar
  1023.   if (document.getElementById("urlbar-container")) {
  1024.     // left or right mouse button
  1025.     if (event.button === 0 || event.button === 2)
  1026.       acm_popupItemSelect(event, popup, true);
  1027.  
  1028.     // middle mouse button opens a new tab; fix for bug 295498
  1029.     else if (event.button === 1) {
  1030.       var new_url = acm_suggestions[acm_treePopup.view.selection.currentIndex].URL;
  1031.       acm_popupExists = false;
  1032.       popup.hidePopup();
  1033.       // open tab in foreground or background, based on user preference
  1034.       var back = acm_getPreference(ACM_LOAD_TAB_IN_BACKGROUND);   
  1035.       var newtab = getBrowser().addTab(new_url);  
  1036.       if (!back)
  1037.         getBrowser().selectedTab = newtab;
  1038.     }
  1039.   }
  1040.   // on a dialog's location bar
  1041.   else 
  1042.     acm_popupItemSelect(event, popup, false);
  1043. }
  1044.  
  1045. // handles selection of a popup item
  1046. function acm_popupItemSelect(event, popup, loadURL)
  1047. {
  1048.   var new_url = acm_suggestions[acm_treePopup.view.selection.currentIndex].URL;
  1049.   if (loadURL)
  1050.     window._content.document.location.href = new_url;  
  1051.   else
  1052.     document.getElementById("urlbar").value = new_url;
  1053.   acm_popupExists = false; 
  1054.   popup.hidePopup();
  1055. }
  1056.  
  1057. // handles mouse-over events for popup items
  1058. function acm_popupMouseMove(event)
  1059. {
  1060.   // update the tree selection
  1061.   acm_popupTreePos = acm_treePopup.view.selection.currentIndex;
  1062. }
  1063.  
  1064. // prevents the popup from appearing after a clickngo middle mouse click on a new tab
  1065. function acm_urlbarMouseUp(event)
  1066. {
  1067.   // only for the browser location bar
  1068.   if (document.getElementById("urlbar-container") && event.button === 1)
  1069.     acm_preventPopup = true;
  1070. }
  1071.  
  1072. // opens a URL from a non-browser's location bar; code taken from 
  1073. // chrome://content/browser/openLocation.js::open()
  1074. function acm_loadURL()
  1075. {
  1076.   if ("arguments" in window && window.arguments.length >= 1)
  1077.     browser = window.arguments[0];
  1078.  
  1079.   var url;
  1080.   var postData = {};
  1081.   if (browser)
  1082.     url = browser.getShortcutOrURI(document.getElementById("urlbar").value, postData);
  1083.   else
  1084.     url = document.getElementById("urlbar").value;
  1085.  
  1086.   try {
  1087.     // Whichever target we use for the load, we allow third-party services to
  1088.     // fixup the URI
  1089.     switch (document.getElementById("openWhereList").value) {
  1090.       case "0":
  1091.         browser.loadURI(url, null, postData.value, true);
  1092.         break;
  1093.       case "1":
  1094.         window.opener.delayedOpenWindow(getBrowserURL(), "all,dialog=no",
  1095.                                         url, postData.value, null, null, true);
  1096.         break;
  1097.       case "3":
  1098.         if (browser.getBrowser && browser.getBrowser().localName == "tabbrowser") 
  1099.           browser.delayedOpenTab(url, null, null, postData.value, true);
  1100.         else 
  1101.           browser.loadURI(url, null, postData.value, true); // Just do a normal load.
  1102.         break;
  1103.     }
  1104.   }
  1105.   catch(exception) {
  1106.   }
  1107.  
  1108.   if (acm_prefs) {
  1109.     var str = Components.classes["@mozilla.org/supports-string;1"]
  1110.                         .createInstance(Components.interfaces.nsISupportsString);
  1111.     str.data = document.getElementById("urlbar").value;
  1112.     acm_prefs.setComplexValue("general.open_location.last_url",
  1113.                               Components.interfaces.nsISupportsString, str);
  1114.     acm_prefs.setIntPref("general.open_location.last_window_choice", 
  1115.                          document.getElementById("openWhereList").value);
  1116.   }
  1117.  
  1118.   // Delay closing slightly to avoid timing bug on Linux.
  1119.   window.close();
  1120.   return false;
  1121. }
  1122.  
  1123. // matches the user-typed prefix against the provided list of AutocompleteCandidate's and sorts the results
  1124. function acm_getSortedSuggestionList(list)
  1125. {
  1126.   acm_bookfirst = acm_getPreference(ACM_BOOKMARKS_FIRST);
  1127.  
  1128.   if (acm_getPreference(ACM_SORTBY) === "default") 
  1129.     return acm_getFilteredCandidates(list).sort(acm_defaultCompareFun);
  1130.   else if (acm_getPreference(ACM_SORTBY) === "top") 
  1131.     return acm_getFilteredCandidates(list).sort(acm_topCompareFun);
  1132.   else if (acm_getPreference(ACM_SORTBY) === "mfu") 
  1133.     return acm_getFilteredCandidates(list).sort(acm_mfuCompareFun);
  1134.   else if (acm_getPreference(ACM_SORTBY) === "mru") 
  1135.     return acm_getFilteredCandidates(list).sort(acm_mruCompareFun);
  1136.   else if (acm_getPreference(ACM_SORTBY) === "alpha_address") {
  1137.     acm_checkBookFirst = true;
  1138.     var matches = acm_getFilteredCandidates(list).sort(acm_alphaURLCompareFun);
  1139.     acm_checkBookFirst = false;
  1140.     return matches;
  1141.   }
  1142.   else if (acm_getPreference(ACM_SORTBY) === "alpha_title") 
  1143.     return acm_getFilteredCandidates(list).sort(acm_alphaTitleCompareFun);
  1144.   else  // fallback on error
  1145.     return acm_getFilteredCandidates(list).sort(acm_defaultCompareFun);
  1146. }
  1147.  
  1148. // Firefox default ordering
  1149. // - if the URL ends with a slash (Web path) or if it has been manually typed, 
  1150. //   add 5 to its visit count
  1151. // - sort in order of decreasing visit count
  1152. // - on a visit count tie, place Web paths first
  1153. // - if both addresses represent Web paths, sort in ascending alphabetical order,
  1154. //   after ignoring common protocols and prefixes
  1155. // The order calculated by this function is not exactly the order Firefox would 
  1156. // end up with, since an extension currently has no way of finding out whether 
  1157. // an address has been manually typed.
  1158. function acm_defaultCompareFun(candidateA, candidateB)
  1159. {
  1160.   if (acm_bookfirst) {
  1161.     if (candidateA.source === ACM_SOURCE_BOOKMARKS && candidateB.source !== ACM_SOURCE_BOOKMARKS)
  1162.       return -1;
  1163.     else if (candidateA.source !== ACM_SOURCE_BOOKMARKS && candidateB.source === ACM_SOURCE_BOOKMARKS)
  1164.       return 1;
  1165.   }
  1166.  
  1167.   var AisWebpath = candidateA.webpath;
  1168.   var BisWebpath = candidateB.webpath;
  1169.  
  1170.   var countA = candidateA.visit_count;
  1171.   if (AisWebpath)
  1172.     countA += 5;
  1173.  
  1174.   var countB = candidateB.visit_count;
  1175.   if (BisWebpath)
  1176.     countB += 5;  
  1177.  
  1178.   if (countA > countB) // place A before B
  1179.     return -1; 
  1180.   else if (countA < countB) 
  1181.     return 1; 
  1182.   else {
  1183.     if (AisWebpath && !BisWebpath) 
  1184.       return -1;
  1185.     else if (!AisWebpath && BisWebpath)
  1186.       return 1;
  1187.     else 
  1188.       return acm_alphaURLCompareFun(candidateA, candidateB);
  1189.   }
  1190. }
  1191.  
  1192. // place domain addresses first sorting them in ascending alphabetical order;
  1193. // sort the rest also in ascending alphabetical order 
  1194. function acm_topCompareFun(candidateA, candidateB)
  1195. {
  1196.   if (acm_bookfirst) {
  1197.     if (candidateA.source === ACM_SOURCE_BOOKMARKS && candidateB.source !== ACM_SOURCE_BOOKMARKS)
  1198.       return -1;
  1199.     else if (candidateA.source !== ACM_SOURCE_BOOKMARKS && candidateB.source === ACM_SOURCE_BOOKMARKS)
  1200.       return 1;
  1201.   }
  1202.  
  1203.   var AisDomain = candidateA.domain;
  1204.   var BisDomain = candidateB.domain; 
  1205.  
  1206.   if (AisDomain && !BisDomain)  // place A before B
  1207.     return -1;
  1208.   else if (!AisDomain && BisDomain)
  1209.     return 1;
  1210.   else
  1211.     return acm_alphaURLCompareFun(candidateA, candidateB);
  1212. }
  1213.  
  1214. // sort in order of decreasing visit count,
  1215. // or in the case of a tie, in ascending alphabetical order 
  1216. function acm_mfuCompareFun(candidateA, candidateB)
  1217. {
  1218.   if (acm_bookfirst) {
  1219.     if (candidateA.source === ACM_SOURCE_BOOKMARKS && candidateB.source !== ACM_SOURCE_BOOKMARKS)
  1220.       return -1;
  1221.     else if (candidateA.source !== ACM_SOURCE_BOOKMARKS && candidateB.source === ACM_SOURCE_BOOKMARKS)
  1222.       return 1;
  1223.   }
  1224.  
  1225.   var countA = candidateA.visit_count;
  1226.   var countB = candidateB.visit_count;
  1227.  
  1228.   if (countA > countB) // place A before B
  1229.     return -1; 
  1230.   else if (countA < countB) 
  1231.     return 1; 
  1232.   else 
  1233.     return acm_alphaURLCompareFun(candidateA, candidateB);
  1234. }
  1235.  
  1236. // sort in order of decreasing last visit date,
  1237. // or in the case of a tie, in ascending alphabetical order 
  1238. function acm_mruCompareFun(candidateA, candidateB)
  1239. {
  1240.   if (acm_bookfirst) {
  1241.     if (candidateA.source === ACM_SOURCE_BOOKMARKS && candidateB.source !== ACM_SOURCE_BOOKMARKS)
  1242.       return -1;
  1243.     else if (candidateA.source !== ACM_SOURCE_BOOKMARKS && candidateB.source === ACM_SOURCE_BOOKMARKS)
  1244.       return 1;
  1245.   }
  1246.  
  1247.   var visitA = candidateA.last_visit;
  1248.   var visitB = candidateB.last_visit;
  1249.  
  1250.   if (visitA > visitB) // place A before B
  1251.     return -1; 
  1252.   else if (visitA < visitB) 
  1253.     return 1; 
  1254.   else 
  1255.     return acm_alphaURLCompareFun(candidateA, candidateB);
  1256. }
  1257.  
  1258. // sort in ascending alphabetical order by title
  1259. function acm_alphaTitleCompareFun(candidateA, candidateB)
  1260. {
  1261.   if (acm_bookfirst) {
  1262.     if (candidateA.source === ACM_SOURCE_BOOKMARKS && candidateB.source !== ACM_SOURCE_BOOKMARKS)
  1263.       return -1;
  1264.     else if (candidateA.source !== ACM_SOURCE_BOOKMARKS && candidateB.source === ACM_SOURCE_BOOKMARKS)
  1265.       return 1;
  1266.   }
  1267.  
  1268.   // manufacture appropriate objects to pass as arguments
  1269.   var candA = new Object();
  1270.   candA.strippedURL = candidateA.strippedTitle;
  1271.   candA.URL = "";
  1272.   var candB = new Object();
  1273.   candB.strippedURL = candidateB.strippedTitle;
  1274.   candB.URL = "";
  1275.  
  1276.   return acm_alphaURLCompareFun(candA, candB);
  1277. }
  1278.  
  1279. // sort in ascending alphabetical order by address, after ignoring common protocols and prefixes
  1280. function acm_alphaURLCompareFun(candidateA, candidateB)
  1281. {
  1282.   if (acm_checkBookFirst && acm_bookfirst) {
  1283.     if (candidateA.source === ACM_SOURCE_BOOKMARKS && candidateB.source !== ACM_SOURCE_BOOKMARKS)
  1284.       return -1;
  1285.     else if (candidateA.source !== ACM_SOURCE_BOOKMARKS && candidateB.source === ACM_SOURCE_BOOKMARKS)
  1286.       return 1;
  1287.   }
  1288.  
  1289.   var urlA = candidateA.strippedURL;
  1290.   var urlB = candidateB.strippedURL;
  1291.  
  1292.   if (urlA < urlB)  // place A before B
  1293.     return -1;
  1294.   else if (urlA > urlB)
  1295.     return 1;
  1296.   // sort http://xyz.com before http://www.xyz.com
  1297.   else if (candidateA.URL.length < candidateB.URL.length)
  1298.     return -1;
  1299.   else if (candidateA.URL.length > candidateB.URL.length)
  1300.     return 1;
  1301.   else
  1302.     return 0;
  1303. }
  1304.